import os
import logging
import copy
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir))

from command_parser.command_parser_container import CmdParserContainer
from command_parser.option_parser import *
from utils.common_utils import *
from command_parser.clang_parse.clang_command_generator import ClangGenCommand
from utils.file_type_detect import *
from configure.compiler_configure import *
from configure.env_config import EnvConfig
from command_parser.clang_parse.clang_option_dict import OptionDictFactory, OptionCategory
from base.context import Context
from utils.filelock import FileLock

class ClangParser(CmdParserContainer):
    def __init__(self, command=[], context=None, compiler="", options=[], cwd="",):
        self.command = command
        self.context = context
        self.compiler = compiler
        self.options = options
        self.cwd = cwd
        self.cmdgen = ClangGenCommand(context=context, cwd=self.cwd)
        self.option_dict = None
    
    def parse_command(self, command=[]):
        if not command:
            command = self.command
        self.compiler = real_binary_path(command[0])
        argv = command[1:]
        EnvConfig.check_clang_version(context=self.context, clang_bin=self.compiler)
        self.option_dict = OptionDictFactory.get_option_dict_by_ver(ver=self.context.CLANG_VERSION)
        self.options = split_pass_option(self.build_options(parse_at_option(argv)))
        
        return self.compiler, self.options
    
    def build_options(self, raw_options=[]):
        options_length = len(raw_options)
        new_options = []
        i = 0
        
        while i < options_length:
            raw_option = raw_options[i]
            if not raw_option.startswith('-'):
                option = Option("", [raw_option], PREFIX_MATCH, OptionCategory.SOURCE_FILE, True)

            else:
                option = get_simple_option(option=raw_option, option_dict=self.option_dict.get_simple_dict())
                if option is None:
                    option = get_space_option(prefix=raw_option, params=raw_options[i + 1] if (i + 1) < options_length else None, option_dict=self.option_dict.get_space_dict())
                    if option is not None:
                        i += 1
                    if option is None:
                        option = get_equal_option(option=raw_option, option_dict=self.option_dict.get_equal_dict())
                        if option is None:
                            option = get_match_option(option=raw_option, option_dict=self.option_dict.get_match_dict())
                            if option is None:
                                option = get_unknown_option(option=raw_option, params=raw_options[i + 1] if i + 1 < len(options_length) else None, OptionCategory=OptionCategory)
                                if option.type is PREFIX_SPACE:
                                    i += 1
            i += 1
            new_options.append(option)
            
        return new_options
    
    def get_action_type(self):
        ext_source_files = []
        ext_input_files = []
        
        preprocess_action = ""
        compile_action = ""
        link_action = ""
        action = PREPROCESS
        
        for item in self.options:
            if not item.option:
                """
                Check input file's type. We only get the extended info of source&header file. That
                will be used to judge the type of command, like pre-process and compile.
                """
                ext = os.path.splitext(item.parameter[0])[1]
                if ext and ext[1:]:
                    ext = ext[1:]
                    if ext in list(EXTS_LANG.keys()):
                        ext_source_files.append(ext)
                    elif ext in EXTS_HEADER:
                        pass
                    ext_input_files.append(ext)
                continue
            if item.option == '-x' and item.parameter:
                ext = item.parameter[0]
                if ext in set(EXTS_LANG.values()):
                    ext_source_files.append(ext)
                continue
            if item.option == '-E' or \
                    item.option == '-S':
                preprocess_action = PREPROCESS
                continue
            if item.option == '-c':
                compile_action = COMPILE
                continue
            if item.option == '-o':
                link_action = LINK

        # If it has source files but lack of '-c', it's compile link command.
        if ext_source_files and not compile_action:
            link_action = LINK

        # If lack of input files, but recognized link or compile.
        # We change it to pre-process.
        if not ext_input_files and (link_action or compile_action):
            action = PREPROCESS
        elif preprocess_action:
            action = preprocess_action
        elif compile_action:
            action = compile_action
        elif link_action:
            action = link_action
            for ext in ext_source_files:
                if ext in list(EXTS_LANG.keys()):
                    action = COMPILE_LINK
                    break

        if action is not PREPROCESS:
            action = self.update_action_type(action)
        return action 
    
    def update_action_type(self, action):
        action = self.__filter_options_ck_action(raw_action=action)
        return action
    
    
    def get_output_options(self):
        outputs = []
        for index in range(len(self.options) - 1, -1, -1):
            # For '-o' option, the last is valid.
            if self.options[index].option == '-o':
                outputs.append(self.options[index])
                break
        return outputs
    
    def get_input_options(self):
        inputs = []
        for item in self.options:
            if not item.option or item.option == '-':
                inputs.append(item)
        return inputs
    
    def __filter_options_ck_action(self, raw_action):
        
        if not self.options:
            return

        action = raw_action
        for item in self.options:
            if not item.option:
                # Input files
                if item.parameter and item.parameter[0].startswith('/dev/null'):
                    action = PREPROCESS
                    break

            # I am not sure that this checking is right, but we need to ignore this case.
            if (item.option == '-MT' or item.option == '-MQ') and action != COMPILE:
                action = PREPROCESS
                break
            if item.option == '-o':
                if item.parameter and item.parameter[0].startswith('/dev/null'):
                    action = PREPROCESS
                    break

            if item.option == '-x' and item.parameter:
                if item.parameter[0].endswith('-header'):
                    action = PREPROCESS
                    break
        return action
    
    def get_original_command(self):
        return self.cmdgen.generate_original_command(compiler=self.compiler, options=self.options)
    
    def get_divided_compile_command(self):
        self.auto_check_output_options()
        return self.cmdgen.generate_divided_compile_command(compiler=self.compiler, options=self.options)

    def get_divided_link_command(self):
        return self.cmdgen.generate_divided_link_command(compiler=self.compiler, options=self.options)
    
    def get_maple_compile_command(self):
        compiler = self.context.MAPLE_CLANG
        if is_cpp_compiler(self.compiler):
            compiler = self.context.MAPLE_CLANGCPP
        
        return self.cmdgen.generate_assemble_maple_ir_command(parser=self, compiler=compiler, options=self.options)
    
    def get_maple_compile_command_with_sys_headers(self):
        return self.get_maple_compile_command()
    
    def get_retry_maple_compile_command(self):
        # a simple wrapper to unify interface
        return self.get_maple_compile_command()
        
    def check_is_src_file(self, option):
        if option is None:
            return False
        
        if option.option is '-':
            return True
        
        # TODO: 这里可以以配置的方式写入
        spec_file = ["/dev/null"]
        if option.parameter:
            file = option.parameter[0]
            if file in spec_file:
                return True
            
            ext = os.path.splitext(file)[1]
            if ext and ext[1:] in EXTS_LANG.keys():
                return True
        
        return False
    
    def resolve_symbolic_inputs(self):
        
        input_opts = self.get_input_options()
        
        try:
            for input_opt in input_opts:
                real_files = []
                try:
                    index = self.options.index(input_opt)
                except IndexError:
                    continue
                suffix_opt = None
                for input in input_opt.parameter:
                    real_file = os.path.realpath(input)
                    raw_ext = os.path.splitext(input)[1][1:]
                    real_ext = os.path.splitext(real_file)[1][1:]
                    if not self.index_option_by_name(opt_name='-x', end_index=index):
                        lang = EXTS_LANG.get(raw_ext, "")
                        if not lang:
                            lang = EXTS_LANG.get(real_ext, "")
                        if not lang:
                            logging.warning("Can't find correct language for file %s", input)
                            real_file = input
                        if suffix_opt is None:
                            suffix_opt = Option(option="-x", parameter=[lang], type=PREFIX_SPACE)
                    real_files.append(real_file)
                
                input_opt.parameter = real_files
                if suffix_opt:
                    self.insert_option_by_index(option=suffix_opt, index=index)
        except Exception as e:
            logging.exception("TODO")
    
    def auto_check_output_options(self):
        if not self.get_output_options():
            out_opt = self.generate_temporary_target()
            self.extend_options_by_list(out_opt)
    
    def generate_temporary_target(self):
        inputs = self.get_input_options()
        output_name = os.path.splitext(os.path.basename(inputs[0].parameter[0]))[0] + '.o'
        real_output_name = os.path.join(self.cwd, output_name)
        output_option = Option(option="-o", parameter=[real_output_name], type=PREFIX_SPACE)

        return [output_option]
    
    def parse_dep_files(self):
        
        local_options = copy.deepcopy(self.options)
        prep_match_options = ["-MD", "-MM", "-M", "-MF", "-MP", "-MQ", "-MT", "-MMD", "-Wp,", "-Wa,", "-o"]
        local_parser = ClangParser(context=self.context, compiler=self.compiler, options=local_options, cwd=self.cwd)
        local_parser.delete_options_by_name(prep_match_options)
        
        extend_prep_options = []
        
        p_option = Option(option='-E', type=PREFIX_SIMPLE)
        extend_prep_options.append(p_option)
        
        p_option = Option(option="-MM", type=PREFIX_SIMPLE)
        extend_prep_options.append(p_option)
        
        output = os.path.basename(self.get_output_options()[0].parameter[0])
        dep_out_file = os.path.join(local_parser.cwd, output+".d")
        p_option = Option(option="-MF", parameter=[dep_out_file],type=PREFIX_SPACE)
        extend_prep_options.append(p_option)
        
        w_option = Option(option="-Wno-error", type=PREFIX_SIMPLE)
        extend_prep_options.append(w_option)
        
        local_parser.extend_options_by_list(extend_list=extend_prep_options)
        
        command = local_parser.get_original_command()
        with FileLock(file_name=dep_out_file) as lock:
            try:
                run_notty_command(command)
            except:
                logging.warning("parser dependencies failed")
            
            remain_dep_files = []
            if os.path.exists(dep_out_file):
                with open(dep_out_file, 'r') as f:
                    dep_content = f.read().strip()
                
                os.remove(dep_out_file)
                dep_content = dep_content.split(":")[1].lstrip()
                dep_files = dep_content.split(" ")
                for file in dep_files:
                    file_abs_path = os.path.abspath(file)
                    
                    if not os.path.exists(file_abs_path):
                        continue
                    
                    if file_abs_path.startswith("/dev"):
                        continue
                    
                    remain_dep_files.append(file)
            else:
                logging.error("Do not find the dep file of %s" % dep_out_file)
        return remain_dep_files
    
if __name__ == "__main__":
    command = "/usr/bin/clang -DLINUX -DNPATCHED -DX32_COMPILE -Dpov_EXPORTS -I/root/cb-multios/include -I/root/cb-multios/include/libpov -I/root/cb-multios/include/tiny-AES128-C -m32 -fPIC -fno-builtin -fcommon -w -g3 -pedantic -O0 -std=gnu99 -MD -MT include/libpov/CMakeFiles/pov.dir/pcre_fullinfo.c.o -MF include/libpov/CMakeFiles/pov.dir/pcre_fullinfo.c.o.d -o include/libpov/CMakeFiles/pov.dir/pcre_fullinfo.c.o -c /root/cb-multios/include/libpov/pcre_fullinfo.c".split()
    parser = ClangParser(command=command, context=Context())
    compiler, options = parser.parse_command()
    for opt in options:
        print(opt.get_option(), opt.category)
    print(parser.get_action_type())
    print(parser.context.CLANG_VERSION)
        
        